package jmine.tec.web.servlet;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import jmine.tec.services.ServicesController;
import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.Service;
import jmine.tec.services.api.ServiceProperties;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.factory.ServiceCreationException;
import jmine.tec.services.api.factory.ServiceFactory;
import bancosys.tec.persist.controller.PersistenceEnabledController;

/**
 * @author takeshi.fukushima
 */
public class ServiceRequestParser {

    private static final int NUM_10 = 10;

    private static final String DRYRUN = "param.dryrun";

    public static final int MAX_SERVICE_PAGE_SIZE = 2000;

    private static final String[] SERVICE_PREFIX;

    private static final String SERVICE_PARAMETER_PREFIX = "param.";

    private static final int SERVICE_PARAMETER_PREFIX_LENGTH = SERVICE_PARAMETER_PREFIX.length();

    private static final int SERVICE_PREFFIX_BASE_LENGTH = "service.".length();

    private static final Map<String, Integer> SERVICE_PARAMETER_INDEX = new HashMap<String, Integer>();

    private ServiceFactory serviceFactory;

    private ServicesController servicesController;

    static {
        SERVICE_PREFIX = new String[MAX_SERVICE_PAGE_SIZE];
        for (int i = 0; i < MAX_SERVICE_PAGE_SIZE; i++) {
            SERVICE_PREFIX[i] = "service" + i + ".";
            SERVICE_PARAMETER_INDEX.put(SERVICE_PREFIX[i], i);
        }
    }


    public static final Comparator<String> SERVICE_ORDER_COMPARATOR = new Comparator<String>() {

        public int compare(String o1, String o2) {
            return SERVICE_PARAMETER_INDEX.get(o1) - SERVICE_PARAMETER_INDEX.get(o2);
        }
    };

    /**
     * Returns the number of decimal digits from a given number. Ignores sign symbol
     * 
     * @param number the number
     * @return int
     */
    public static int numberOfDecimalDigits(int number) {
        if (number < 0) {
            return numberOfDecimalDigits(-number);
        } else if (number < NUM_10) {
            return 1;
        } else {
            return 1 + numberOfDecimalDigits(number / NUM_10);
        }
    }

    /**
     * Executa o parse do mapa da request para uma página de serviços.
     * 
     * @param request {@link HttpServletRequest}
     * @return {@link ServicesPage}
     * @throws ServiceCreationException Se ocorrer erro na criação do serviço.
     */
    public ServicesPage parse(HttpServletRequest request) throws ServiceCreationException {
        Map<String, Map<String, String>> params = this.parseServiceParameters(request);
        ServicesPage page = new ServicesPage();
        ServiceProperties rootServiceProperties = new ServiceProperties(this.servicesController);
        page.setName("http request service page");

        boolean dryrun = false;
        for (Map<String, String> map : params.values()) {
            Service<PersistenceEnabledController> service = this.extractService(map, page, rootServiceProperties);
            page.addService(service);
            dryrun = "on".equals(map.get(DRYRUN));
        }
        page.setDryrun(dryrun);
        return page;
    }

    /**
     * @param request {@link HttpServletRequest}
     * @return {@link Map}
     */
    @SuppressWarnings("unchecked")
    public Map<String, Map<String, String>> parseServiceParameters(HttpServletRequest request) {
        Map<String, Map<String, String>> params = new TreeMap<String, Map<String, String>>(SERVICE_ORDER_COMPARATOR);
        Set<Entry<String, String[]>> set = request.getParameterMap().entrySet();
        for (Entry<String, String[]> entry : set) {
            String paramName = entry.getKey();
            for (int i = 0; i < MAX_SERVICE_PAGE_SIZE; i++) {
                String prefix = SERVICE_PREFIX[i];
                if (paramName.startsWith(prefix)) {
                    if (!params.containsKey(prefix)) {
                        params.put(prefix, new HashMap<String, String>());
                    }
                    params.get(prefix)
                            .put(paramName.substring(SERVICE_PREFFIX_BASE_LENGTH + numberOfDecimalDigits(i)), entry.getValue()[0]);
                    break;
                }
            }
        }
        return params;
    }

    /**
     * Extrai um servico do mapa passado. O mapa ja nao tem o prefixo do indice do servico.
     * 
     * @param serviceMap o {@link Map}
     * @param page a pagina a qual o servico pertence
     * @param rootServiceProperties o ServiceProperties 'parent'
     * @return {@link Service}
     * @throws ServiceCreationException e
     */
    @SuppressWarnings("unchecked")
    private Service<PersistenceEnabledController> extractService(Map<String, String> serviceMap, ServicesPage page,
            ServiceProperties rootServiceProperties) throws ServiceCreationException {
        String action = serviceMap.get("action");
        String serviceName = serviceMap.get("name");
        ServiceProperties properties = this.collectServiceProperties(serviceMap, rootServiceProperties);
        return (Service<PersistenceEnabledController>) this.serviceFactory.getService(this.getAction(action), serviceName, properties);
    }

    /**
     * Coleta os parametros de um servico
     * 
     * @param serviceMap o mapa
     * @param rootServiceProperties o {@link ServiceProperties} parent
     * @return {@link ServiceProperties}
     */
    public ServiceProperties collectServiceProperties(Map<String, String> serviceMap, ServiceProperties rootServiceProperties) {
        Set<Entry<String, String>> entrySet = serviceMap.entrySet();
        Map<String, Object> parameters = this.servicesController.instantiateMap();
        for (Entry<String, String> entry : entrySet) {
            if (entry.getKey().startsWith(SERVICE_PARAMETER_PREFIX)) {
                String paramName = entry.getKey().substring(SERVICE_PARAMETER_PREFIX_LENGTH);
                parameters.put(paramName, entry.getValue());
            }
        }
        return new ServiceProperties(this.servicesController, rootServiceProperties, parameters);
    }

    /**
     * Tenta traduzir a action
     * 
     * @param action String
     * @return {@link ActionsEnum}
     */
    private ActionsEnum getAction(String action) {
        return ActionsEnum.valueOf(action.toUpperCase());
    }

    /**
     * @param servicesController the servicesController to set
     */
    public void setServicesController(ServicesController servicesController) {
        this.servicesController = servicesController;
    }

    /**
     * @param serviceFactory the serviceFactory to set
     */
    public void setServiceFactory(ServiceFactory serviceFactory) {
        this.serviceFactory = serviceFactory;
    }

}
